﻿using System;
using System.Linq;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
using NetWebScript.Metadata;
using NetWebScript.Remoting.Serialization;

namespace NetWebScript.Remoting
{
    /// <summary>
    /// Utility class to create a transparent proxy.
    /// </summary>
    public static class ScriptTransparentProxy
    {
        // This class is "well-known" of compiler

        /// <summary>
        /// Create a transparent proxy of type T, forwarding all calls to <paramref name="realProxy"/>.
        /// </summary>
        /// <typeparam name="T">A <see cref="System.MarshalByRefObject"/> or an interface.</typeparam>
        /// <param name="realProxy">Proxy that will receive method call made on returned object.</param>
        /// <returns>A new transparent proxy</returns>
        public static T Create<T>(ScriptRealProxy realProxy)
        {
            // Note: In script version, this code is NOT used. Code is generated by compiler.
            return (T)new NetToScriptRealProxy(typeof(T), realProxy).GetTransparentProxy();
        }

        private class NetToScriptRealProxy : RealProxy
        {
            private readonly ScriptRealProxy scriptRealProxy;

            internal NetToScriptRealProxy (Type type, ScriptRealProxy scriptRealProxy) : base(type)
            {
                this.scriptRealProxy = scriptRealProxy;
            }

            public override IMessage Invoke(IMessage msg)
            {
                IConstructionCallMessage ctorCall = msg as IConstructionCallMessage;
                if (ctorCall != null)
                {
                    return new ConstructionResponse(null, ctorCall);
                }
                IMethodCallMessage methodCall = msg as IMethodCallMessage;
                if (methodCall == null)
                {
                    throw new ArgumentException("Script transparent proxy only supports method calls.");
                }
                var cref = CRefToolkit.GetCRef(methodCall.MethodBase);
                var type = MetadataProvider.Current.GetTypeMetadata(methodCall.MethodBase.DeclaringType);
                var method = type.Methods.FirstOrDefault(m => m.CRef == cref);
                object result;
                try
                {
                    result = scriptRealProxy.Invoke(type.Name, method.Name, methodCall.Args);
                }
                catch (Exception e)
                {
                    return new ReturnMessage(e, methodCall);
                }
                return new ReturnMessage(result, new object[0], 0, null, methodCall);
            }
        }

    }
}
